package com.ganbo.redpacket.service.impl;

import com.ganbo.redpacket.entity.RedRobRecord;
import com.ganbo.redpacket.exception.CustomException;
import com.ganbo.redpacket.exception.ExceptionCode;
import com.ganbo.redpacket.model.requset.HandOutReq;
import com.ganbo.redpacket.model.requset.RobReq;
import com.ganbo.redpacket.service.RedPacketAsyncService;
import com.ganbo.redpacket.service.RedPacketService;
import com.ganbo.redpacket.utils.RedPacketUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 抢发红包服务实现类
 * Created by gan on 2020/1/12 14:58.
 */
@Service
@Slf4j
public class RedPacketServiceImpl implements RedPacketService {

    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    RedPacketAsyncService redPacketAsyncService;

    private static final String keyPrefix = "redis:red:packet:";

    @Override
    public String handOut(HandOutReq req) {
        if (req.getTotal() > 0 && req.getAmount() > 0) {
            //生成随机金额
            List<Integer> list = RedPacketUtil.divideRedPackage(req.getAmount(), req.getTotal());

            //生成红包全局唯一标识,并将随机金额、个数入缓存
            String timestamp = String.valueOf(System.nanoTime());
            String redId = new StringBuffer(keyPrefix).append(req.getUserId()).append(":").append(timestamp).toString();
            redisTemplate.opsForList().leftPushAll(redId, list);

            //redis缓存红包个数
            String redTotalKey = redId + ":total";
            redisTemplate.opsForValue().set(redTotalKey, req.getTotal());

            //异步记录红包发出的记录-包括个数与随机金额
            redPacketAsyncService.recordRedPacket(req, redId, list);
            return redId;
        } else {
            log.info("发红包参数校验不合法..");
            throw CustomException.build(ExceptionCode.PARAMETER_ERROR);
        }
    }

    @Override
    public BigDecimal rob(RobReq req) {
        String redId = req.getRedId();
        Integer userId = req.getUserId();
        ValueOperations valueOperations = redisTemplate.opsForValue();

        //判断用户是否抢过红包
        Object o = valueOperations.get(redId + userId + "-lock");
        if (!Objects.isNull(o)) {
            return new BigDecimal(o.toString());
        }

        //上锁:一个红包每个人只能抢一次随机金额；一个人每次只能抢到红包的一次随机金额  即要永远保证 1对1 的关系
        final String lockKey = redId + userId + "-lock";
        Boolean lock = valueOperations.setIfAbsent(lockKey, redId);
        redisTemplate.expire(lockKey, 24L, TimeUnit.HOURS);

        if (lock) {
            //点红包
            String redTotalKey = redId + ":total";
            Object total = valueOperations.get(redTotalKey);
            if (total == null || Integer.parseInt(total.toString()) < 1) {
                log.info("红包已经抢完了.");
                throw CustomException.build(ExceptionCode.REDPACKET_ZERO);
            }

            //红包个数减一
            int currentTotal = Integer.parseInt(total.toString());
            valueOperations.set(redTotalKey, currentTotal - 1);

            //获取红包金额
            Object value = redisTemplate.opsForList().rightPop(redId);
            if (value == null) {
                throw CustomException.build(ExceptionCode.REDPACKET_ZERO);
            }
            BigDecimal result = new BigDecimal(value.toString()).divide(new BigDecimal(100));   //转换为元

            //设置抢红包记录
            valueOperations.set(redId + userId + "-lock", result, 24L, TimeUnit.HOURS);

            //数据库保存用户抢红包记录
            RedRobRecord redRobRecord = RedRobRecord.builder().userId(userId).redPacket(redId).amount(result).robTime(new Date()).build();
            redPacketAsyncService.recordRodRedPacket(redRobRecord);

            log.info("当前用户抢到红包了: userId:{}, key:{}, amount:", userId, redId, result);
            return result;
        } else {
            log.error("并发太高了,分布式锁触发");
            throw CustomException.build(ExceptionCode.REDPACKET_REPETITION_CLIENT);
        }


    }
}
